package org.linlinjava.litemall.web.aop;


import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.linlinjava.litemall.web.annotation.repeat.RequestKeyParam;
import org.linlinjava.litemall.web.annotation.repeat.RequestRepeatLock;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * @Author: penglingyu
 * @Date: 2024/3/28 14:43
 */
@Aspect
@Component
@Slf4j
public class RequestRepeatLockAspect {

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    //配置织⼊点
    @Pointcut("@annotation(org.linlinjava.litemall.web.annotation.repeat.RequestRepeatLock)")
    public void cachePointCut() {
    }

    @Around("cachePointCut()")
    public Object doRequestRepeatLockAspect(ProceedingJoinPoint joinPoint) throws
            Throwable {
        // 1 获得重载后的⽅法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass()
                .getMethod(signature.getName(),
                        signature.getMethod().getParameterTypes());
        // 2 确定⽅法名后获得该⽅法上⾯配置的注解 RequestRepeatLock
        RequestRepeatLock requestRepeatLock = method.getAnnotation(RequestRepeatLock.class);
        // 构造缓存唯⼀key
        String cacheKey = getLockKey(joinPoint);
        boolean isLock = setLock(cacheKey, requestRepeatLock.timeout(), requestRepeatLock.unit());
        if (!isLock) {
            return requestRepeatLock.message();
        }
        return joinPoint.proceed();
    }

    /**
     * 按钮提交，加锁逻辑
     *
     * @param key
     * @param expire
     * @return
     */
    public boolean setLock(String key, long expire, TimeUnit timeUnit) {
        Boolean flag;
        try {
            // 原⼦⾏命令⼀个：set k1 v1 nx ex 10
            flag = redisTemplate.execute(
                    (RedisCallback<Boolean>) connection ->
                            connection.set(key.getBytes(),
                                    IdUtil.fastSimpleUUID().getBytes(), Expiration.from(expire, timeUnit),
                                    RedisStringCommands.SetOption.ifAbsent()));
            log.info("**********execute action:{} ", flag);
            return flag;
        } catch (Exception e) {
            log.error("set redis occured an exception", e);
        }
        return false;
    }

    /**
     * 唯⼀key业务规则获取
     *
     * @param joinPoint
     * @return
     */
    public String getLockKey(ProceedingJoinPoint joinPoint) {
        StringBuilder stringBuilder = new StringBuilder();
        //获取连接点的⽅法签名对象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //Method对象
        Method method = methodSignature.getMethod();
        System.out.println("******method: " + method);
        System.out.println("******method.getName(): " + method.getName());
        //获取Method对象上的注解对象
        RequestRepeatLock requestRepeatLock = method.getAnnotation(RequestRepeatLock.class);
        //获取⽅法参数
        final Object[] args = joinPoint.getArgs();
        for (Object element : args) {
            System.out.println("******args: " + element);
        }
        //获取Method对象上所有的注解
        final Parameter[] parameters = method.getParameters();
        System.out.println(parameters.length);
        /* for (Parameter element : parameters) {
        System.out.println("******parameters: " + element.getName());
        }*/
        for (int i = 0; i < parameters.length; i++) {
            final RequestKeyParam keyParam = parameters[i].getAnnotation(RequestKeyParam.class);
            //如果属性不是RequestKeyParam注解，则不处理
            if (keyParam == null) {
                continue;
            }
            //如果属性是RequestKeyParam注解，则拼接 连接符 "& + RequestKeyParam"
            stringBuilder.append(requestRepeatLock.delimiter()).append(args[i]);
        }
        System.out.println("******stringBuilder拼接有RequestKeyParam注解: " + stringBuilder);
        //注解@RequestKeyParam可以放在⽅法的参数上，
        // 也可以放在对象的属性上，所以这⾥需要进⾏两次判断，⼀次是获取⽅法上的注解，⼀次是获取对象⾥⾯属性上的注解。
        //如果⽅法上没有加RequestKeyParam注解
        if (StringUtils.isEmpty(stringBuilder.toString())) {
            //获取⽅法上的多个注解（为什么是两层数组：因为第⼆层数组是只有⼀个元素的数组）
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            //循环注解
            for (int i = 0; i < parameterAnnotations.length; i++) {
                final Object object = args[i];
                //获取注解类中所有的属性字段
                final Field[] fields = object.getClass().getDeclaredFields();
                for (Field field : fields) {
                    //判断字段上是否有RequestKeyParam注解
                    final RequestKeyParam annotation = field.getAnnotation(RequestKeyParam.class);
                    //如果没有，跳过
                    if (annotation == null) {
                        continue;
                    }
                    //如果有，设置Accessible为true（为true时可以使⽤反射访问私有变量，否则不能访问私有变量）
                    field.setAccessible(true);
                    //如果属性是RequestKeyParam注解，则拼接 连接符" & + RequestKeyParam"
                    stringBuilder.append(requestRepeatLock.delimiter()).append(ReflectionUtils.getField(field, object));
                }
            }
            System.out.println("******stringBuilder拼接有RequestKeyParam注解: " + stringBuilder);
        }
        String userId = "";
        if (requestRepeatLock.userKey()) {
            userId = ""; // 获取⽤户id
        }
        // 构造唯⼀key
        String key = SecureUtil.md5(userId + method.getName());
        stringBuilder.append(key);
        return requestRepeatLock.keyPrefix() + stringBuilder;
    }






}
